package cn.alauncher.tcpdemo;

import java.io.*;
import java.net.*;

import cn.alauncher.tcpdemo.callback.TCPClientcallBack;
import cn.alauncher.tcpdemo.tool.RegularJudgment;
import cn.alauncher.tcpdemo.utils.ByteUtil;
import cn.alauncher.tcpdemo.utils.Constant;

/**
 * 该类实现TCP客户端各操作
 * <p>
 * 开心跳后
 * 1.心跳发送没有回复3次会断
 * 2.发送心跳失败会断
 * 正常检查断的情况
 * 3.发送消息到服务器失败会断
 * 4.发送消息编码失败会断
 * 5.接收消息内容长度小于等于0会断
 * 6.接收消息异常会断
 * 7.本地连接的一些状态检查,不符合正常情况断开,5秒检查一次
 *
 * @author KK
 */
public final class TCPclient {

    private static InputStream inputStream; // 从TCP来的输入流
    private static OutputStream outputStream;// 向TCP输出的输出流
    private boolean bConnected = false;// 连接标识

    public boolean isbConnected() {
        return bConnected;
    }

    private boolean LisTh = true;// 控制监听线程运行
    private boolean ChTh = true;// 控制重连线程运行
    private Socket TCPSocket;// TCP连接套接字

    public Socket getTCPSocket() {
        return TCPSocket;
    }

    // private String Hreatdata = "F";// 心跳数据
    private String HEXHreatData = "";
    private boolean Ishreat = false;// 是否开启心跳
    private long HreatTime = 60;// 心跳间隔时间，单位s
    private boolean IsReCon = false;// 是否需要重连
    private long ReconnectionTime = 30;// 重连时间，单位s

    private TCPClientcallBack TCPClientcallBack = null;
    private String ServerIP = null;
    private int ServerPort = 0;
    private boolean Isstartscces = false;// 第一次是否初始化成功
    private byte Hreatnum = 0;// 心跳发送没有收到回复次数
    private byte HreatMaxnum = 3;// 心跳发送没有收到回复次数超过该值就认为tcp连续断开,默认3次都没有回复就认为通讯断开
    private byte MaxNumSate = 5;//多久检查一次当前连接的本地状态,单位s,默认5秒
    private boolean IsLegi = false;//IP和端口是否合法,不合法都不开启重连/心跳线程

    /**
     * TCP初始化，初始化成功返回1，失败返回0
     *
     * @param IP        待连接服务器IP
     * @param Port      待连接服务器端口
     * @param clallback 回调对象
     * @return 0表示连接成功，-1表示地址错误，-2表示连接失败
     */
    public byte startTCPclient(String IP, int Port, cn.alauncher.tcpdemo.callback.TCPClientcallBack clallback) {
        if (RegularJudgment.isboolIp(IP) && Port > 0) {
            IsLegi = true;
            try {
                ServerIP = IP;
                ServerPort = Port;
                TCPClientcallBack = clallback;
                TCPSocket = new Socket(IP, Port);// 创建连接字
                try {
                    TCPSocket.setKeepAlive(true);// 开启tcp连接保活
                } catch (SocketException e1) {
                    // TODO 自动生成的 catch 块
                    e1.printStackTrace();
                }
                inputStream = new DataInputStream(TCPSocket.getInputStream());// 输入流
                outputStream = new DataOutputStream(TCPSocket.getOutputStream());// 输出流
                LisenDataTread T = new LisenDataTread();
                LisTh = true;
                T.start();
                bConnected = true;
                Isstartscces = true;
                TCPClientcallBack.Hasconnect();// 回调执行连接上了
                System.out.println("TCP connect scccess!!");
                return 0;
            } catch (UnknownHostException e) {
                bConnected = false;
                TCPClientcallBack.ConnectErro();
                // TODO 自动生成的 catch 块
                e.printStackTrace();
                return -1;
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
                bConnected = false;
                TCPClientcallBack.ConnectErro();
                return -2;
            }
        }
        return -1;
    }

    /**
     * 开启重连
     *
     * @param time 重连时间间隔，单位s
     */
    public void StartReConncet(long time) {
        if (IsLegi) {
            IsReCon = true;
            ReconnectionTime = time;
            if (Ishreat == false) {
                Thread T = new MyHreat();
                T.start();
            }
        }

    }

    /**
     * 开启心跳
     *
     * @param HreatMSG
     * @param time     心跳间隔时间，单位s
     */
    public void StartHreat(String HreatMSG, long time) {
        if (IsLegi) {
            Ishreat = true;
            HreatTime = time;
            HEXHreatData = HreatMSG;
            if (IsReCon == false) {
                Thread T = new MyHreat();
                T.start();
            }
        }
    }


    /**
     * 销毁本地连接,一般在销毁客户端对象或连接是时调用
     */
    private void destorySocket() {
        bConnected = false;
        try {
            inputStream.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            outputStream.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            TCPSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 销毁和停止
     */
    public void destroyobj() {
        LisTh = false;
        ChTh = false;
        destorySocket();
        bConnected = false;
    }

    /**
     * tcp连接断开
     */
    private void Disconnected() {
        if (bConnected) {
            Exception e = new Exception("TCPClient Disconnected.");
            e.printStackTrace();
            System.out.println(e);
            destorySocket();
            TCPClientcallBack.Disconnected();//连接断开，执行回调
        }
    }

    /**
     * tcp客户端发送数据
     *
     * @param msg 待发送的字符串
     */
    public byte TCPClientSend(String msg) {
        byte[] sendbuffer;
        byte rs = -2;
        try {
            sendbuffer = msg.getBytes("utf-8");
            rs = TCPClientsendbyteData(sendbuffer, 0, sendbuffer.length);
        } catch (UnsupportedEncodingException e) {
            // TODO 自动生成的 catch 块
            Disconnected();
            e.printStackTrace();
            rs = -1;
        }
        return rs;
    }


    public byte TCPClientSendHEX(String hex) {
        byte[] sendbuffer;
        byte rs = -2;
        // sendbuffer = msg.getBytes("utf-8");
        sendbuffer = ByteUtil.HexToByteArr(hex);
        rs = TCPClientsendbyteData(sendbuffer, 0, sendbuffer.length);
        return rs;
    }

    /**
     * @param SendBuff 待发送数据帧数组
     * @param off      数组起始位置
     * @param length   要发送数据个数
     * @return 0表示发送成功，-1表示还没连接，-2表示发送失败
     */
    public byte TCPClientsendbyteData(byte[] SendBuff, int off, int length) {
        try {
            if (bConnected) {
                if (TCPSocketState()) {
                    outputStream.write(SendBuff, off, length);
                    outputStream.flush();// 输出到缓冲区
                    return 0;
                } else {
                    Disconnected();//如果本地状态失效则连接已经断开了
                    return -1;
                }
            } else {
                return -1;
            }
        } catch (IOException e) {
            Disconnected();
            return -2;
        } // 把数据发送出去
    }

    /**
     * 根据TCPSocket的各种状态返回本地连接的状态
     *
     * @return
     */
    private boolean TCPSocketState() {
        return TCPSocket.isConnected() && !TCPSocket.isClosed() && TCPSocket.isBound() && !TCPSocket.isInputShutdown()
                && !TCPSocket.isOutputShutdown();
    }

    /**
     * 开辟一个线程监视TCP数据
     *
     * @author zhao
     */
    private class LisenDataTread extends Thread {
        public void run() {
            int rcnum = -1;// 防止断开后多次回调报断开
            while (LisTh) {
                if (TCPSocketState() && bConnected) {
                    rcnum = -1;
                    int numBytes = -1;
                    try {
                        int datanum = inputStream.available();
                        if (datanum > 0) {
                            byte readbuff[] = new byte[datanum];
                            numBytes = inputStream.read(readbuff);// 获取TCP输入流数据帧放入数据readbuff里
                            if (numBytes <= 0) {
                                Disconnected();// 读取到数据长度小于1则已经断开了
                            } else {
                                boolean istrue = false;// 过滤发送过来的数据是0x0ascci的错误情况
                                for (int k = 0; k < 8 && k < numBytes; k++) {
                                    if (readbuff[k] != 0) {
                                        istrue = true;
                                    }
                                }
                                if (istrue) {
                                    if (Ishreat) {//心跳开启才检查是否是心跳的回复
                                        // String rstr = new String(readbuff, 0, numBytes, "utf-8");
                                        byte readbufft[] = new byte[numBytes];
                                        System.arraycopy(readbuff, 0, readbufft, 0, numBytes);
                                        if (ByteUtil.ByteArrToHex(readbuff).equals(Constant.HREAT_REASULT)) {
                                            // System.out.println("hreat back.");
                                            bConnected = true;// 有心跳回复说明连接正常
                                            Hreatnum = 0;
                                        } else {
                                            TCPClientcallBack.MssageArrived(readbufft, numBytes);
                                        }
                                    } else {
                                        byte readbufft[] = new byte[numBytes];
                                        System.arraycopy(readbuff, 0, readbufft, 0, numBytes);
                                        TCPClientcallBack.MssageArrived(readbufft, numBytes);
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        Disconnected();// 读取到数据长度小于1则已经断开了
                    }
                    try {
                        Thread.sleep(1);//在没有连接状态下减低CPU使用率
                    } catch (InterruptedException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                } else {
                    if (rcnum == -1) {//防止多次报通讯失败
                        Disconnected();// 读取到数据长度小于1则已经断开了
                        rcnum = 0;
                    }
                    try {
                        Thread.sleep(10);//在没有连接状态下减低CPU使用率
                    } catch (InterruptedException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    private class MyHreat extends Thread {
        public void run() {
            long RCnum = 0;//重连时间计时变量
            long Hrnum = 0;//心跳时间计时变量
            long Statenum = 0;//本地连接状态检查
            while (ChTh) {
                try {
                    if (bConnected) {
                        if (Ishreat) {
                            Hrnum++;
                            if (Hrnum >= HreatTime * 100) {
                                try {
                                    Hrnum = 0;
                                    // byte[] sendbuffer = Hreatdata.getBytes("utf-8");
                                    byte[] sendbuffer = ByteUtil.HexToByteArr(HEXHreatData);
                                    outputStream.write(sendbuffer);
                                    outputStream.flush();// 输出到缓冲区
                                    // System.out.println("do Hreat  & Hrnum = " + Hrnum);
                                    Hreatnum++;
                                    if (Hreatnum > HreatMaxnum) {
                                        Disconnected();// 读取到数据长度小于1则已经断开了
                                    }
                                } catch (IOException e1) {
                                    System.out.println(e1);
                                    // Disconnected();// 读取到数据长度小于1则已经断开了
                                }
                            }
                        }
                        Statenum++;
                        if (Statenum >= MaxNumSate * 100) {
                            Statenum = 0;
                            if (!TCPSocketState()) {
                                Disconnected();//本地连接状态检查到断开就断开
                            }
                        }
                    } else if (!bConnected && IsReCon) {
                        RCnum++;
                        if (RCnum >= ReconnectionTime * 100) {
                            RCnum = 0;
                            try {
                                Hreatnum = 0;// 重连把心跳发送次数复位
                                TCPSocket = new Socket(ServerIP, ServerPort);
                                inputStream = new DataInputStream(TCPSocket.getInputStream());// 输入流
                                outputStream = new DataOutputStream(TCPSocket.getOutputStream());// 输出流
                                bConnected = true;
                                if (!Isstartscces) {
                                    LisenDataTread T = new LisenDataTread();
                                    T.start();
                                    Isstartscces = true;
                                }
                                TCPClientcallBack.Hasconnect();// 回调执行连接上了
                            } catch (IOException e) {
                                bConnected = false;
                                TCPClientcallBack.ConnectErro();
                                // TODO 自动生成的 catch 块
                            }
                        }
                    }
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO 自动生成的 catch 块
                    e.printStackTrace();
                }
            }
        }
    }
}
